A FileTextBox control in WPF
Have you ever wanted a text file to be displayed in readonly fashion in a WPF window? Perhaps you have a log file and you want the UI to stay synced with the log updates to the file? Maybe you simply want to write a WPF version of WinTail?
Well, I just wrote a control in which you can set file path to a DependencyProperty and the UI will stay up to date. I created a child of TextBox called FileTextBox and implemented FileSystemWatcher.
Update: 6/16/2014 – Added AutoScroll.
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
namespace WpfSharp.UserControls
{
public class FileTextBox : TextBox
{
#region Private fields
private bool _ChangedHandlerAdded;
private bool _CreatedHandlerAdded;
private bool _DeletedHandlerAdded;
private bool _RenameHandlerAdded;
#endregion
#region constructor
public FileTextBox()
{
AcceptsReturn = true;
IsReadOnly = true;
AutoScroll = true;
}
#endregion
#region Properties
/// <summary>
/// If true, the FileTextBox will always scroll to the end when updated.
/// </summary>
public bool AutoScroll { get; set; }
#endregion
#region File Dependency Property
public string File
{
get { return (string)GetValue(FileProperty); }
set { SetValue(FileProperty, value); }
}
// Using a DependencyProperty as the backing store for File. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FileProperty =
DependencyProperty.Register("File", typeof(string), typeof(FileTextBox), new FrameworkPropertyMetadata(OnFilePropertyChanged));
private static void OnFilePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var ftb = sender as FileTextBox;
if (ftb == null || args.NewValue == null || string.IsNullOrWhiteSpace(args.NewValue.ToString()))
{
return;
}
var dir = GetDirectory(ref args);
if (!string.IsNullOrWhiteSpace(dir) && Directory.Exists(dir))
{
ftb.Watcher.Path = Path.GetDirectoryName(args.NewValue.ToString());
ftb.Watcher.Filter = Path.GetFileName(args.NewValue.ToString());
ftb.AddEvents();
ftb.Watcher.EnableRaisingEvents = true;
ftb.UpdateFile();
}
else
{
ftb.Text = string.Empty;
}
}
private static string GetDirectory(ref DependencyPropertyChangedEventArgs args)
{
try
{
return Path.GetDirectoryName(args.NewValue.ToString());
}
catch (Exception)
{
return null;
}
}
#endregion
private FileSystemWatcher Watcher
{
get { return _Watcher ?? (_Watcher = BuildWatcher()); }
} private FileSystemWatcher _Watcher;
private FileSystemWatcher BuildWatcher()
{
var watcher = new FileSystemWatcher { NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName };
return watcher;
}
public void OnFileDeleted(object sender, FileSystemEventArgs e)
{
Dispatcher.Invoke(UpdateFile);
}
public void OnFileChanged(object sender, FileSystemEventArgs e)
{
Dispatcher.Invoke(UpdateFile);
}
public void OnFileCreated(object sender, FileSystemEventArgs e)
{
Dispatcher.Invoke(() =>
{
UpdateFile();
EnableRaiseEvents();
});
}
public void OnFileRenamed(object sender, RenamedEventArgs e)
{
Dispatcher.Invoke(UpdateFile);
}
private void EnableRaiseEvents()
{
Dispatcher.Invoke(() =>
{
if (!Watcher.EnableRaisingEvents)
Watcher.EnableRaisingEvents = true;
});
}
private void UpdateFile()
{
if (!System.IO.File.Exists(File))
{
Text = string.Empty;
return;
}
using (var fs = new FileStream(File, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (var sr = new StreamReader(fs))
{
Text = sr.ReadToEnd();
if (AutoScroll)
ScrollToEnd();
}
}
}
private void AddEvents()
{
if (!_CreatedHandlerAdded)
{
Watcher.Created += OnFileCreated;
_CreatedHandlerAdded = true;
}
if (!_ChangedHandlerAdded)
{
Watcher.Changed += OnFileChanged;
_ChangedHandlerAdded = true;
}
if (!_DeletedHandlerAdded)
{
Watcher.Deleted += OnFileDeleted;
_DeletedHandlerAdded = true;
}
if (!_RenameHandlerAdded)
{
Watcher.Renamed += OnFileRenamed;
_RenameHandlerAdded = true;
}
}
}
}
As always, feedback is appreciated.
